// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.

package nachos.machine;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.PrivilegedAction;
import java.util.Random;

/**
 * Thrown when an assertion fails.
 */
class AssertionFailureError extends Error {
    AssertionFailureError() {
        super();
    }

    AssertionFailureError(String message) {
        super(message);
    }
}

/**
 * Provides miscellaneous library routines.
 */
public final class Lib {
    /**
     * Prevent instantiation.
     */
    private Lib() {
    }

    private static Random random = null;

    /**
     * Seed the random number generater. May only be called once.
     *
     * @param randomSeed the seed for the random number generator.
     */
    public static void seedRandom(long randomSeed) {
        assertTrue(random == null);
        random = new Random(randomSeed);
    }

    /**
     * Return a random integer between 0 and <i>range - 1</i>. Must not be
     * called before <tt>seedRandom()</tt> seeds the random number generator.
     *
     * @param range a positive value specifying the number of possible
     *              return values.
     * @return a random integer in the specified range.
     */
    public static int random(int range) {
        assertTrue(range > 0);
        return random.nextInt(range);
    }

    /**
     * Return a random double between 0.0 (inclusive) and 1.0 (exclusive).
     *
     * @return a random double between 0.0 and 1.0.
     */
    public static double random() {
        return random.nextDouble();
    }

    /**
     * 断言 expresssion 的值为 true，如果不是，系统退出并报错
     *
     * @param expression 要断言的值
     */
    public static void assertTrue(boolean expression) {
        if (!expression)
            throw new AssertionFailureError();
    }

    /**
     * Asserts that <i>expression</i> is <tt>true</tt>. If not, then Nachos
     * exits with the specified error message.
     *
     * @param expression the expression to assert.
     * @param message    the error message.
     */
    public static void assertTrue(boolean expression, String message) {
        if (!expression)
            throw new AssertionFailureError(message);
    }

    /**
     * Asserts that this call is never made. Same as <tt>assertTrue(false)</tt>.
     */
    public static void assertNotReached() {
        assertTrue(false);
    }

    /**
     * Asserts that this call is never made, with the specified error messsage.
     * Same as <tt>assertTrue(false, message)</tt>.
     *
     * @param message the error message.
     */
    public static void assertNotReached(String message) {
        assertTrue(false, message);
    }

    /**
     * Print <i>message</i> if <i>flag</i> was enabled on the command line. To
     * specify which flags to enable, use the -d command line option. For
     * example, to enable flags a, c, and e, do the following:
     *
     * <p>
     * <pre>nachos -d ace</pre>
     *
     * <p>
     * Nachos uses several debugging flags already, but you are encouraged to
     * add your own.
     *
     * @param flag    the debug flag that must be set to print this message.
     * @param message the debug message.
     */
    public static void debug(char flag, String message) {
        if (test(flag))
            System.out.println(message);
    }

    /**
     * Tests if <i>flag</i> was enabled on the command line.
     *
     * @param flag the debug flag to test.
     * @return <tt>true</tt> if this flag was enabled on the command line.
     */
    public static boolean test(char flag) {
        if (debugFlags == null)
            return false;
        else if (debugFlags[(int) '+'])
            return true;
        else if (flag >= 0 && flag < 0x80 && debugFlags[(int) flag])
            return true;
        else
            return false;
    }

    /**
     * Enable all the debug flags in <i>flagsString</i>.
     *
     * @param flagsString the flags to enable.
     */
    public static void enableDebugFlags(String flagsString) {
        if (debugFlags == null)
            debugFlags = new boolean[0x80];

        char[] newFlags = flagsString.toCharArray();
        for (int i = 0; i < newFlags.length; i++) {
            char c = newFlags[i];
            if (c >= 0 && c < 0x80)
                debugFlags[(int) c] = true;
        }
    }

    /**
     * Debug flags specified on the command line.
     */
    private static boolean debugFlags[];

    /**
     * Read a file, verifying that the requested number of bytes is read, and
     * verifying that the read operation took a non-zero amount of time.
     *
     * @param file     the file to read.
     * @param position the file offset at which to start reading.
     * @param buf      the buffer in which to store the data.
     * @param offset   the buffer offset at which storing begins.
     * @param length   the number of bytes to read.
     */
    public static void strictReadFile(OpenFile file, int position,
                                      byte[] buf, int offset, int length) {
        long startTime = Machine.timer().getTime();
        assertTrue(file.read(position, buf, offset, length) == length);
        long finishTime = Machine.timer().getTime();
        assertTrue(finishTime > startTime);
    }

    /**
     * Load an entire file into memory.
     *
     * @param file the file to load.
     * @return an array containing the contents of the entire file, or
     * <tt>null</tt> if an error occurred.
     */
    public static byte[] loadFile(OpenFile file) {
        int startOffset = file.tell();

        int length = file.length();
        if (length < 0)
            return null;

        byte[] data = new byte[length];

        file.seek(0);
        int amount = file.read(data, 0, length);
        file.seek(startOffset);

        if (amount == length)
            return data;
        else
            return null;
    }

    /**
     * Take a read-only snapshot of a file.
     *
     * @param file the file to take a snapshot of.
     * @return a read-only snapshot of the file.
     */
    public static OpenFile cloneFile(OpenFile file) {
        OpenFile clone = new ArrayFile(loadFile(file));

        clone.seek(file.tell());

        return clone;
    }

    /**
     * Convert a short into its little-endian byte string representation.
     *
     * @param array  the array in which to store the byte string.
     * @param offset the offset in the array where the string will start.
     * @param value  the value to convert.
     */
    public static void bytesFromShort(byte[] array, int offset, short value) {
        array[offset + 0] = (byte) ((value >> 0) & 0xFF);
        array[offset + 1] = (byte) ((value >> 8) & 0xFF);
    }

    /**
     * Convert an int into its little-endian byte string representation.
     *
     * @param array  the array in which to store the byte string.
     * @param offset the offset in the array where the string will start.
     * @param value  the value to convert.
     */
    public static void bytesFromInt(byte[] array, int offset, int value) {
        array[offset + 0] = (byte) ((value >> 0) & 0xFF);
        array[offset + 1] = (byte) ((value >> 8) & 0xFF);
        array[offset + 2] = (byte) ((value >> 16) & 0xFF);
        array[offset + 3] = (byte) ((value >> 24) & 0xFF);
    }

    /**
     * Convert an int into its little-endian byte string representation, and
     * return an array containing it.
     *
     * @param value the value to convert.
     * @return an array containing the byte string.
     */
    public static byte[] bytesFromInt(int value) {
        byte[] array = new byte[4];
        bytesFromInt(array, 0, value);
        return array;
    }

    /**
     * Convert an int into a little-endian byte string representation of the
     * specified length.
     *
     * @param array  the array in which to store the byte string.
     * @param offset the offset in the array where the string will start.
     * @param length the number of bytes to store (must be 1, 2, or 4).
     * @param value  the value to convert.
     */
    public static void bytesFromInt(byte[] array, int offset,
                                    int length, int value) {
        assertTrue(length == 1 || length == 2 || length == 4);

        switch (length) {
            case 1:
                array[offset] = (byte) value;
                break;
            case 2:
                bytesFromShort(array, offset, (short) value);
                break;
            case 4:
                bytesFromInt(array, offset, value);
                break;
        }
    }

    /**
     * Convert to a short from its little-endian byte string representation.
     *
     * @param array  the array containing the byte string.
     * @param offset the offset of the byte string in the array.
     * @return the corresponding short value.
     */
    public static short bytesToShort(byte[] array, int offset) {
        return (short) ((((short) array[offset + 0] & 0xFF) << 0) |
                (((short) array[offset + 1] & 0xFF) << 8));
    }

    /**
     * Convert to an unsigned short from its little-endian byte string
     * representation.
     *
     * @param array  the array containing the byte string.
     * @param offset the offset of the byte string in the array.
     * @return the corresponding short value.
     */
    public static int bytesToUnsignedShort(byte[] array, int offset) {
        return (((int) bytesToShort(array, offset)) & 0xFFFF);
    }

    /**
     * Convert to an int from its little-endian byte string representation.
     *
     * @param array  the array containing the byte string.
     * @param offset the offset of the byte string in the array.
     * @return the corresponding int value.
     */
    public static int bytesToInt(byte[] array, int offset) {
        return (int) ((((int) array[offset + 0] & 0xFF) << 0) |
                (((int) array[offset + 1] & 0xFF) << 8) |
                (((int) array[offset + 2] & 0xFF) << 16) |
                (((int) array[offset + 3] & 0xFF) << 24));
    }

    /**
     * Convert to an int from a little-endian byte string representation of the
     * specified length.
     *
     * @param array  the array containing the byte string.
     * @param offset the offset of the byte string in the array.
     * @param length the length of the byte string.
     * @return the corresponding value.
     */
    public static int bytesToInt(byte[] array, int offset, int length) {
        assertTrue(length == 1 || length == 2 || length == 4);

        switch (length) {
            case 1:
                return array[offset];
            case 2:
                return bytesToShort(array, offset);
            case 4:
                return bytesToInt(array, offset);
            default:
                return -1;
        }
    }

    /**
     * Convert to a string from a possibly null-terminated array of bytes.
     *
     * @param array  the array containing the byte string.
     * @param offset the offset of the byte string in the array.
     * @param length the maximum length of the byte string.
     * @return a string containing the specified bytes, up to and not
     * including the null-terminator (if present).
     */
    public static String bytesToString(byte[] array, int offset, int length) {
        int i;
        for (i = 0; i < length; i++) {
            if (array[offset + i] == 0)
                break;
        }

        return new String(array, offset, i);
    }

    /**
     * Mask out and shift a bit substring.
     *
     * @param bits   the bit string.
     * @param lowest the first bit of the substring within the string.
     * @param size   the number of bits in the substring.
     * @return the substring.
     */
    public static int extract(int bits, int lowest, int size) {
        if (size == 32)
            return (bits >> lowest);
        else
            return ((bits >> lowest) & ((1 << size) - 1));
    }

    /**
     * Mask out and shift a bit substring.
     *
     * @param bits   the bit string.
     * @param lowest the first bit of the substring within the string.
     * @param size   the number of bits in the substring.
     * @return the substring.
     */
    public static long extract(long bits, int lowest, int size) {
        if (size == 64)
            return (bits >> lowest);
        else
            return ((bits >> lowest) & ((1L << size) - 1));
    }

    /**
     * Mask out and shift a bit substring; then sign extend the substring.
     *
     * @param bits   the bit string.
     * @param lowest the first bit of the substring within the string.
     * @param size   the number of bits in the substring.
     * @return the substring, sign-extended.
     */
    public static int extend(int bits, int lowest, int size) {
        int extra = 32 - (lowest + size);
        return ((extract(bits, lowest, size) << extra) >> extra);
    }

    /**
     * Test if a bit is set in a bit string.
     *
     * @param flag the flag to test.
     * @param bits the bit string.
     * @return <tt>true</tt> if <tt>(bits & flag)</tt> is non-zero.
     */
    public static boolean test(long flag, long bits) {
        return ((bits & flag) != 0);
    }

    /**
     * Creates a padded upper-case string representation of the integer
     * argument in base 16.
     *
     * @param i an integer.
     * @return a padded upper-case string representation in base 16.
     */
    public static String toHexString(int i) {
        return toHexString(i, 8);
    }

    /**
     * Creates a padded upper-case string representation of the integer
     * argument in base 16, padding to at most the specified number of digits.
     *
     * @param i   an integer.
     * @param pad the minimum number of hex digits to pad to.
     * @return a padded upper-case string representation in base 16.
     */
    public static String toHexString(int i, int pad) {
        String result = Integer.toHexString(i).toUpperCase();
        while (result.length() < pad)
            result = "0" + result;
        return result;
    }

    /**
     * Divide two non-negative integers, round the quotient up to the nearest
     * integer, and return it.
     *
     * @param a the numerator.
     * @param b the denominator.
     * @return <tt>ceiling(a / b)</tt>.
     */
    public static int divRoundUp(int a, int b) {
        assertTrue(a >= 0 && b > 0);

        return ((a + (b - 1)) / b);
    }

    /**
     * Load and return the named class, or return <tt>null</tt> if the class
     * could not be loaded.
     *
     * @param className the name of the class to load.
     * @return the loaded class, or <tt>null</tt> if an error occurred.
     */
    public static Class tryLoadClass(String className) {
        try {
            return ClassLoader.getSystemClassLoader().loadClass(className);
        } catch (Throwable e) {
            return null;
        }
    }

    /**
     * Load and return the named class, terminating Nachos on any error.
     *
     * @param className the name of the class to load.
     * @return the loaded class.
     */
    public static Class loadClass(String className) {
        try {
            return ClassLoader.getSystemClassLoader().loadClass(className);
        } catch (Throwable e) {
            Machine.terminate(e);
            return null;
        }
    }

    /**
     * 创建并返回一个新的给定名称的类的实例，使用无参数的构造函数
     *
     * @param className 要实例化的类的名称
     * @return 一个新的类的实例
     */
    public static Object constructObject(String className) {
        try {
            // kamil - workaround for Java 1.4
            // Thanks to Ka-Hing Cheung for the suggestion.
            // Fixed for Java 1.5 by geels
            Class[] param_types = new Class[0];
            Object[] params = new Object[0];
            return loadClass(className).getConstructor(param_types).newInstance(params);
        } catch (Throwable e) {
            Machine.terminate(e);
            return null;
        }
    }

    /**
     * Verify that the specified class extends or implements the specified
     * superclass.
     *
     * @param cls      the descendant class.
     * @param superCls the ancestor class.
     */
    public static void checkDerivation(Class cls, Class superCls) {
        Lib.assertTrue(superCls.isAssignableFrom(cls));
    }

    /**
     * Verifies that the specified class is public and not abstract, and that a
     * constructor with the specified signature exists and is public.
     *
     * @param cls            the class containing the constructor.
     * @param parameterTypes the list of parameters.
     */
    public static void checkConstructor(Class cls, Class[] parameterTypes) {
        try {
            Lib.assertTrue(Modifier.isPublic(cls.getModifiers()) &&
                    !Modifier.isAbstract(cls.getModifiers()));
            Constructor constructor = cls.getConstructor(parameterTypes);
            Lib.assertTrue(Modifier.isPublic(constructor.getModifiers()));
        } catch (Exception e) {
            Lib.assertNotReached();
        }
    }

    /**
     * Verifies that the specified class is public, and that a non-static
     * method with the specified name and signature exists, is public, and
     * returns the specified type.
     *
     * @param cls            the class containing the non-static method.
     * @param methodName     the name of the non-static method.
     * @param parameterTypes the list of parameters.
     * @param returnType     the required return type.
     */
    public static void checkMethod(Class cls, String methodName,
                                   Class[] parameterTypes, Class returnType) {
        try {
            Lib.assertTrue(Modifier.isPublic(cls.getModifiers()));
            Method method = cls.getMethod(methodName, parameterTypes);
            Lib.assertTrue(Modifier.isPublic(method.getModifiers()) &&
                    !Modifier.isStatic(method.getModifiers()));
            Lib.assertTrue(method.getReturnType() == returnType);
        } catch (Exception e) {
            Lib.assertNotReached();
        }
    }

    /**
     * Verifies that the specified class is public, and that a static method
     * with the specified name and signature exists, is public, and returns the
     * specified type.
     *
     * @param cls            the class containing the static method.
     * @param methodName     the name of the static method.
     * @param parameterTypes the list of parameters.
     * @param returnType     the required return type.
     */
    public static void checkStaticMethod(Class cls, String methodName,
                                         Class[] parameterTypes,
                                         Class returnType) {
        try {
            Lib.assertTrue(Modifier.isPublic(cls.getModifiers()));
            Method method = cls.getMethod(methodName, parameterTypes);
            Lib.assertTrue(Modifier.isPublic(method.getModifiers()) &&
                    Modifier.isStatic(method.getModifiers()));
            Lib.assertTrue(method.getReturnType() == returnType);
        } catch (Exception e) {
            Lib.assertNotReached();
        }
    }

    /**
     * Verifies that the specified class is public, and that a non-static field
     * with the specified name and type exists, is public, and is not final.
     *
     * @param cls       the class containing the field.
     * @param fieldName the name of the field.
     * @param fieldType the required type.
     */
    public static void checkField(Class cls, String fieldName,
                                  Class fieldType) {
        try {
            Lib.assertTrue(Modifier.isPublic(cls.getModifiers()));
            Field field = cls.getField(fieldName);
            Lib.assertTrue(field.getType() == fieldType);
            Lib.assertTrue(Modifier.isPublic(field.getModifiers()) &&
                    !Modifier.isStatic(field.getModifiers()) &&
                    !Modifier.isFinal(field.getModifiers()));
        } catch (Exception e) {
            Lib.assertNotReached();
        }
    }

    /**
     * Verifies that the specified class is public, and that a static field
     * with the specified name and type exists and is public.
     *
     * @param cls       the class containing the static field.
     * @param fieldName the name of the static field.
     * @param fieldType the required type.
     */
    public static void checkStaticField(Class cls, String fieldName,
                                        Class fieldType) {
        try {
            Lib.assertTrue(Modifier.isPublic(cls.getModifiers()));
            Field field = cls.getField(fieldName);
            Lib.assertTrue(field.getType() == fieldType);
            Lib.assertTrue(Modifier.isPublic(field.getModifiers()) &&
                    Modifier.isStatic(field.getModifiers()));
        } catch (Exception e) {
            Lib.assertNotReached();
        }
    }
}
